// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2008-2014 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#include "stdafx.h"
#include "resource.h"
#include "../TortoiseShell/resource.h"
#include "SVNStatusListCtrl.h"
#include <iterator>

#define SVNSLC_COL_VERSION 7

// assign property list

CSVNStatusListCtrl::PropertyList&
CSVNStatusListCtrl::PropertyList::operator= (const char* rhs)
{
    // do you really want to replace the property list?

    assert (properties.empty());
    properties.clear();

    // add all properties in the list

    while ((rhs != NULL) && (*rhs != 0))
    {
        const char* next = strchr (rhs, ' ');

        CString name (rhs, static_cast<int>(next == NULL ? strlen (rhs) : next - rhs));
        properties.insert (std::make_pair (name, CString()));

        rhs = next == NULL ? NULL : next+1;
    }

    // done

    return *this;
}

// collect property names in a set

void CSVNStatusListCtrl::PropertyList::GetPropertyNames (std::set<CString>& names) const
{
    for ( CIT iter = properties.begin(), end = properties.end()
        ; iter != end
        ; ++iter)
    {
        names.insert (iter->first);
    }
}

// get a property value.

const CString& CSVNStatusListCtrl::PropertyList::operator[](const CString& name) const
{
    static const CString empty;
    CIT iter = properties.find (name);

    return iter == properties.end()
        ? empty
        : iter->second;
}

// set a property value.

CString& CSVNStatusListCtrl::PropertyList::operator[](const CString& name)
{
    return properties[name];
}

/// check whether that property has been set on this item.

bool CSVNStatusListCtrl::PropertyList::HasProperty (const CString& name) const
{
    return properties.find (name) != properties.end();
}

// due to frequent use: special check for svn:needs-lock

bool CSVNStatusListCtrl::PropertyList::IsNeedsLockSet() const
{
    static const CString svnNeedsLock (SVN_PROP_NEEDS_LOCK);
    return HasProperty (svnNeedsLock);
}

// remove all entries

void CSVNStatusListCtrl::PropertyList::Clear()
{
    properties.clear();
}

// number of properties
size_t CSVNStatusListCtrl::PropertyList::Count() const
{
    return properties.size();
}

// registry access

void CSVNStatusListCtrl::ColumnManager::ReadSettings
    ( DWORD defaultColumns
    , const CString& containerName)
{
    // defaults

    DWORD selectedStandardColumns = defaultColumns;

    columns.resize (SVNSLC_NUMCOLUMNS);
    for (size_t i = 0; i < SVNSLC_NUMCOLUMNS; ++i)
    {
        columns[i].index = static_cast<int>(i);
        columns[i].width = 0;
        columns[i].visible = true;
        columns[i].relevant = true;
    }

    userProps.clear();

    // where the settings are stored within the registry

    registryPrefix
        = L"Software\\TortoiseSVN\\StatusColumns\\" + containerName;

    // we accept only the current version
    bool valid = (DWORD)CRegDWORD (registryPrefix + L"Version", 0xff) == SVNSLC_COL_VERSION;
    if (valid)
    {
        // read (possibly different) column selection

        selectedStandardColumns
            = CRegDWORD (registryPrefix, selectedStandardColumns);

        // read user-prop lists

        CString userPropList
            = CRegString (registryPrefix + L"UserProps");
        CString shownUserProps
            = CRegString (registryPrefix + L"ShownUserProps");

        ParseUserPropSettings (userPropList, shownUserProps);

        // read column widths

        CString colWidths
            = CRegString (registryPrefix + L"_Width");

        ParseWidths (colWidths);
    }

    // process old-style visibility setting

    SetStandardColumnVisibility (selectedStandardColumns);

    // clear all previously set header columns

    int c = ((CHeaderCtrl*)(control->GetDlgItem(0)))->GetItemCount()-1;
    while (c>=0)
        control->DeleteColumn(c--);

    // create columns

    for (int i = 0, count = GetColumnCount(); i < count; ++i)
        control->InsertColumn (i, GetName(i), LVCFMT_LEFT, IsVisible(i) ? -1 : GetVisibleWidth(i, false));

    // restore column ordering

    if (valid)
        ParseColumnOrder (CRegString (registryPrefix + L"_Order"));
    else
        ParseColumnOrder (CString());

    ApplyColumnOrder();

    // auto-size the columns so we can see them while fetching status
    // (seems the same values will not take affect in InsertColumn)

    for (int i = 0, count = GetColumnCount(); i < count; ++i)
        if (IsVisible(i))
            control->SetColumnWidth (i, GetVisibleWidth (i, true));
}

void CSVNStatusListCtrl::ColumnManager::WriteSettings() const
{
    CRegDWORD regVersion (registryPrefix + L"Version", 0, TRUE);
    regVersion = SVNSLC_COL_VERSION;

    // write (possibly different) column selection

    CRegDWORD regStandardColumns (registryPrefix, 0, TRUE);
    regStandardColumns = GetSelectedStandardColumns();

    // write user-prop lists

    CRegString regUserProps (registryPrefix + L"UserProps", CString(), TRUE);
    regUserProps = GetUserPropList();

    CRegString regShownUserProps (registryPrefix + L"ShownUserProps", CString(), TRUE);
    regShownUserProps = GetShownUserProps();

    // write column widths

    CRegString regWidths (registryPrefix + L"_Width", CString(), TRUE);
    regWidths = GetWidthString();

    // write column ordering

    CRegString regColumnOrder (registryPrefix + L"_Order", CString(), TRUE);
    regColumnOrder = GetColumnOrderString();
}

// read column definitions

int CSVNStatusListCtrl::ColumnManager::GetColumnCount() const
{
    return static_cast<int>(columns.size());
}

bool CSVNStatusListCtrl::ColumnManager::IsVisible (int column) const
{
    size_t index = static_cast<size_t>(column);
    assert (columns.size() > index);

    return columns[index].visible;
}

int CSVNStatusListCtrl::ColumnManager::GetInvisibleCount() const
{
    int invisibleCount = 0;
    for (std::vector<ColumnInfo>::const_iterator it = columns.begin(); it != columns.end(); ++it)
    {
        if (!it->visible)
            invisibleCount++;
    }
    return invisibleCount;
}

bool CSVNStatusListCtrl::ColumnManager::IsRelevant (int column) const
{
    size_t index = static_cast<size_t>(column);
    assert (columns.size() > index);

    return columns[index].relevant;
}

bool CSVNStatusListCtrl::ColumnManager::IsUserProp (int column) const
{
    size_t index = static_cast<size_t>(column);
    assert (columns.size() > index);

    return columns[index].index >= SVNSLC_USERPROPCOLOFFSET;
}

const CString& CSVNStatusListCtrl::ColumnManager::GetName (int column) const
{
    static const UINT standardColumnNames[SVNSLC_NUMCOLUMNS]
        = { IDS_STATUSLIST_COLFILE

          , IDS_STATUSLIST_COLFILENAME
          , IDS_STATUSLIST_COLEXT
          , IDS_STATUSLIST_COLSTATUS

          , IDS_STATUSLIST_COLREMOTESTATUS
          , IDS_STATUSLIST_COLTEXTSTATUS
          , IDS_STATUSLIST_COLPROPSTATUS

          , IDS_STATUSLIST_COLREMOTETEXTSTATUS
          , IDS_STATUSLIST_COLREMOTEPROPSTATUS
          , IDS_STATUSLIST_COLDEPTH
          , IDS_STATUSLIST_COLURL

          , IDS_STATUSLIST_COLLOCK
          , IDS_STATUSLIST_COLLOCKCOMMENT
          , IDS_STATUSLIST_COLLOCKDATE

          , IDS_STATUSLIST_COLAUTHOR
          , IDS_STATUSLIST_COLREVISION
          , IDS_STATUSLIST_COLREMOTEREVISION
          , IDS_STATUSLIST_COLDATE

          , IDS_STATUSLIST_COLMODIFICATIONDATE
          , IDS_STATUSLIST_COLSIZE};

    static CString standardColumnNameStrings[SVNSLC_NUMCOLUMNS];

    // standard columns

    size_t index = static_cast<size_t>(column);
    if (index < SVNSLC_NUMCOLUMNS)
    {
        CString& result = standardColumnNameStrings[index];
        if (result.IsEmpty())
            result.LoadString (standardColumnNames[index]);

        return result;
    }

    // user-prop columns

    if (index < columns.size())
        return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;

    // default: empty

    static const CString empty;
    return empty;
}

int CSVNStatusListCtrl::ColumnManager::GetWidth (int column, bool useDefaults) const
{
    size_t index = static_cast<size_t>(column);
    assert (columns.size() > index);

    int width = columns[index].width;
    if ((width == 0) && useDefaults)
        width = LVSCW_AUTOSIZE_USEHEADER;

    return width;
}

int CSVNStatusListCtrl::ColumnManager::GetVisibleWidth (int column, bool useDefaults) const
{
    return IsVisible (column)
        ? GetWidth (column, useDefaults)
        : 0;
}

// switch columns on and off

void CSVNStatusListCtrl::ColumnManager::SetVisible
    ( int column
    , bool visible)
{
    size_t index = static_cast<size_t>(column);
    assert (index < columns.size());

    if (columns[index].visible != visible)
    {
        columns[index].visible = visible;
        columns[index].relevant |= visible;
        if (!visible)
            columns[index].width = 0;

        control->SetColumnWidth (column, GetVisibleWidth (column, true));
        ApplyColumnOrder();

        control->Invalidate (FALSE);
    }
}

// tracking column modifications

void CSVNStatusListCtrl::ColumnManager::ColumnMoved (int column, int position)
{
    // in front of what column has it been inserted?

    int index = columns[column].index;

    std::vector<int> gridColumnOrder = GetGridColumnOrder();

    size_t visiblePosition = static_cast<size_t>(position);
    size_t columnCount = gridColumnOrder.size();

    for (;    (visiblePosition < columnCount)
           && !columns[gridColumnOrder[visiblePosition]].visible
         ; ++visiblePosition )
    {
    }

    int next = visiblePosition == (columnCount-1)
             ? -1
             : gridColumnOrder[visiblePosition];

    // move logical column index just in front of that "next" column

    columnOrder.erase (std::find ( columnOrder.begin()
                                 , columnOrder.end()
                                 , index));
    columnOrder.insert ( std::find ( columnOrder.begin()
                                   , columnOrder.end()
                                   , next)
                       , index);

    // make sure, invisible columns are still put in front of all others

    ApplyColumnOrder();
}

void CSVNStatusListCtrl::ColumnManager::ColumnResized (int column)
{
    size_t index = static_cast<size_t>(column);
    assert (index < columns.size());
    assert (columns[index].visible);

    int width = control->GetColumnWidth (column);
    columns[index].width = width;

    int propertyIndex = columns[index].index;
    if (propertyIndex >= SVNSLC_USERPROPCOLOFFSET)
        userProps[propertyIndex - SVNSLC_USERPROPCOLOFFSET].width = width;

    control->Invalidate  (FALSE);
}

// call these to update the user-prop list
// (will also auto-insert /-remove new list columns)

void CSVNStatusListCtrl::ColumnManager::UpdateUserPropList
    (const std::map<CTSVNPath, PropertyList>& propertymap)
{
    // collect all user-defined props

    std::set<CString> aggregatedProps;

    for (auto it = propertymap.cbegin(); it != propertymap.cend(); ++it)
        it->second.GetPropertyNames (aggregatedProps);

    itemProps = aggregatedProps;

    // add new ones to the internal list

    std::set<CString> newProps = aggregatedProps;
    for (size_t i = 0, count = userProps.size(); i < count; ++i)
        newProps.erase (userProps[i].name);

    while (newProps.size() && (newProps.size() + userProps.size() > SVNSLC_MAXCOLUMNCOUNT - SVNSLC_USERPROPCOLOFFSET))
        newProps.erase (--newProps.end());

    typedef std::set<CString>::const_iterator CIT;
    for ( CIT iter = newProps.begin(), end = newProps.end()
        ; iter != end
        ; ++iter)
    {
        int index = static_cast<int>(userProps.size())
                  + SVNSLC_USERPROPCOLOFFSET;
        if (columnOrder.size() < SVNSLC_MAXCOLUMNCOUNT)
            columnOrder.push_back (index);

        UserProp userProp;
        userProp.name = *iter;
        userProp.width = 0;

        userProps.push_back (userProp);
    }

    // remove unused columns from control.
    // remove used ones from the set of aggregatedProps.

    for (size_t i = columns.size(); i > 0; --i)
        if (   (columns[i-1].index >= SVNSLC_USERPROPCOLOFFSET)
            && (aggregatedProps.erase (GetName ((int)i-1)) == 0))
        {
            // this user-prop has not been set on any item

            if (!columns[i-1].visible)
            {
                control->DeleteColumn (static_cast<int>(i-1));
                columns.erase (columns.begin() + i-1);
            }
        }

    // aggregatedProps now contains new columns only.
    // we can't use newProps here because some props may have been used
    // earlier but were not in the recent list of used props.
    // -> they are neither in columns[] nor in newProps.

    for ( CIT iter = aggregatedProps.begin(), end = aggregatedProps.end()
        ; iter != end
        ; ++iter)
    {
        // get the logical column index / ID

        int index = -1;
        int width = 0;
        for (size_t i = 0, count = userProps.size(); i < count; ++i)
            if (userProps[i].name == *iter)
            {
                index = static_cast<int>(i) + SVNSLC_USERPROPCOLOFFSET;
                width = userProps[i].width;
                break;
            }

        if (index == -1)
        {
            // property got removed because there were more than SVNSLC_MAXCOLUMNCOUNT-SVNSLC_USERPROPCOLOFFSET
            continue;
        }

        // find insertion position

        std::vector<ColumnInfo>::iterator columnIter = columns.begin();
        std::vector<ColumnInfo>::iterator end2 = columns.end();
        for (; (columnIter != end2) && columnIter->index < index; ++columnIter);
        int pos = static_cast<int>(columnIter - columns.begin());

        ColumnInfo column;
        column.index = index;
        column.width = width;
        column.visible = false;

        columns.insert (columnIter, column);

        // update control

        int result = control->InsertColumn (pos, *iter, LVCFMT_LEFT, GetVisibleWidth(pos, false));
        assert (result != -1);
        UNREFERENCED_PARAMETER(result);
    }

    // update column order

    ApplyColumnOrder();
}

void CSVNStatusListCtrl::ColumnManager::UpdateRelevance
    ( const std::vector<FileEntry*>& files
    , const std::vector<size_t>& visibleFiles
    , const std::map<CTSVNPath, PropertyList>& propertymap)
{
    // collect all user-defined props that belong to shown files

    std::set<CString> aggregatedProps;
    for (size_t i = 0, count = visibleFiles.size(); i < count; ++i)
    {
        auto propEntry = propertymap.find(files[visibleFiles[i]]->GetPath());
        if (propEntry != propertymap.end())
        {
            propEntry->second.GetPropertyNames (aggregatedProps);
        }
    }

    itemProps = aggregatedProps;

    // invisible columns for unused props are not relevant

    for (int i = 0, count = GetColumnCount(); i < count; ++i)
        if (IsUserProp(i) && !IsVisible(i))
        {
            columns[i].relevant
                = aggregatedProps.find (GetName(i)) != aggregatedProps.end();
        }
}

// don't clutter the context menu with irrelevant prop info

bool CSVNStatusListCtrl::ColumnManager::AnyUnusedProperties() const
{
    return columns.size() < userProps.size() + SVNSLC_NUMCOLUMNS;
}

void CSVNStatusListCtrl::ColumnManager::RemoveUnusedProps()
{
    // determine what column indexes / IDs to keep.
    // map them onto new IDs (we may delete some IDs in between)

    std::map<int, int> validIndices;
    int userPropID = SVNSLC_USERPROPCOLOFFSET;

    for (size_t i = 0, count = columns.size(); i < count; ++i)
    {
        int index = columns[i].index;

        if (   itemProps.find (GetName((int)i)) != itemProps.end()
            || columns[i].visible
            || index < SVNSLC_USERPROPCOLOFFSET)
        {
            validIndices[index] = index < SVNSLC_USERPROPCOLOFFSET
                                ? index
                                : userPropID++;
        }
    }

    // remove everything else:

    // remove from columns and control.
    // also update index values in columns

    for (size_t i = columns.size(); i > 0; --i)
    {
        std::map<int, int>::const_iterator iter
            = validIndices.find (columns[i-1].index);

        if (iter == validIndices.end())
        {
            control->DeleteColumn (static_cast<int>(i-1));
            columns.erase (columns.begin() + i-1);
        }
        else
        {
            columns[i-1].index = iter->second;
        }
    }

    // remove from user props

    for (size_t i = userProps.size(); i > 0; --i)
    {
        int index = static_cast<int>(i)-1 + SVNSLC_USERPROPCOLOFFSET;
        if (validIndices.find (index) == validIndices.end())
            userProps.erase (userProps.begin() + i-1);
    }

    // remove from and update column order

    for (size_t i = columnOrder.size(); i > 0; --i)
    {
        std::map<int, int>::const_iterator iter
            = validIndices.find (columnOrder[i-1]);

        if (iter == validIndices.end())
            columnOrder.erase (columnOrder.begin() + i-1);
        else
            columnOrder[i-1] = iter->second;
    }
}

// bring everything back to its "natural" order

void CSVNStatusListCtrl::ColumnManager::ResetColumns (DWORD defaultColumns)
{
    // update internal data

    std::sort (columnOrder.begin(), columnOrder.end());

    for (size_t i = 0, count = columns.size(); i < count; ++i)
    {
        columns[i].width = 0;
        columns[i].visible = (i < 32) && (((defaultColumns >> i) & 1) != 0);
    }

    for (size_t i = 0, count = userProps.size(); i < count; ++i)
        userProps[i].width = 0;

    // update UI

    for (int i = 0, count = GetColumnCount(); i < count; ++i)
        control->SetColumnWidth (i, GetVisibleWidth (i, true));

    ApplyColumnOrder();

    control->Invalidate (FALSE);
}

// initialization utilities

void CSVNStatusListCtrl::ColumnManager::ParseUserPropSettings
    ( const CString& userPropList
    , const CString& shownUserProps)
{
    assert (userProps.empty());

    static CString delimiters (L" ");

    // parse list of visible user-props

    std::set<CString> visibles;

    int pos = 0;
    CString name = shownUserProps.Tokenize (delimiters, pos);
    while (!name.IsEmpty())
    {
        visibles.insert (name);
        name = shownUserProps.Tokenize (delimiters, pos);
    }

    // create list of all user-props

    pos = 0;
    name = userPropList.Tokenize (delimiters, pos);
    while (!name.IsEmpty())
    {
        bool visible = visibles.find (name) != visibles.end();

        UserProp newEntry;
        newEntry.name = name;
        newEntry.width = 0;

        userProps.push_back (newEntry);

        // auto-create columns for visible user-props
        // (others may be added later)

        if (visible)
        {
            ColumnInfo newColumn;
            newColumn.width = 0;
            newColumn.visible = true;
            newColumn.relevant = true;
            newColumn.index = static_cast<int>(userProps.size())
                            + SVNSLC_USERPROPCOLOFFSET - 1;

            columns.push_back (newColumn);
        }

        name = userPropList.Tokenize (delimiters, pos);
    }
}

void CSVNStatusListCtrl::ColumnManager::ParseWidths (const CString& widths)
{
    for (int i = 0, count = widths.GetLength() / 8; i < count; ++i)
    {
        long width = wcstol (widths.Mid (i*8, 8), NULL, 16);
        if (i < SVNSLC_NUMCOLUMNS)
        {
            // a standard column

            columns[i].width = width;
        }
        else if (i >= SVNSLC_USERPROPCOLOFFSET)
        {
            // a user-prop column

            size_t index = static_cast<size_t>(i - SVNSLC_USERPROPCOLOFFSET);

            // someone may have tweaked the registry settings by hand ...

            if (index < userProps.size())
            {
                userProps[index].width = width;

                for (size_t k = 0, count2 = columns.size(); k < count2; ++k)
                    if (columns[k].index == i)
                        columns[k].width = width;
            }
        }
        else
        {
            // there is no such column

            assert (width == 0);
        }
    }
}

void CSVNStatusListCtrl::ColumnManager::SetStandardColumnVisibility
    (DWORD visibility)
{
    for (size_t i = 0; i < SVNSLC_NUMCOLUMNS; ++i)
    {
        columns[i].visible = (visibility & 1) > 0;
        visibility /= 2;
    }
}

void CSVNStatusListCtrl::ColumnManager::ParseColumnOrder
    (const CString& widths)
{
    std::set<int> alreadyPlaced;
    columnOrder.clear();

    // place columns according to valid entries in orderString

    int limit = static_cast<int>(SVNSLC_USERPROPCOLOFFSET + userProps.size());
    for (int i = 0, count = widths.GetLength() / 2; i < count; ++i)
    {
        int index = wcstol (widths.Mid (i*2, 2), NULL, 16);
        if (   (index < SVNSLC_NUMCOLUMNS)
            || ((index >= SVNSLC_USERPROPCOLOFFSET) && (index < limit)))
        {
            alreadyPlaced.insert (index);
            columnOrder.push_back (index);
        }
    }

    // place the remaining colums behind it

    for (int i = 0; i < SVNSLC_NUMCOLUMNS; ++i)
        if (alreadyPlaced.find (i) == alreadyPlaced.end())
            columnOrder.push_back (i);

    for (int i = SVNSLC_USERPROPCOLOFFSET; i < limit; ++i)
        if (alreadyPlaced.find (i) == alreadyPlaced.end())
            columnOrder.push_back (i);
}

// map internal column order onto visible column order
// (all invisibles in front)

std::vector<int> CSVNStatusListCtrl::ColumnManager::GetGridColumnOrder() const
{
    // extract order of used columns from order of all columns

    std::vector<int> result;
    result.reserve (SVNSLC_MAXCOLUMNCOUNT+1);

    size_t colCount = columns.size();
    bool visible = false;

    do
    {
        // put invisible cols in front

        for (size_t i = 0, count = columnOrder.size(); i < count; ++i)
        {
            int index = columnOrder[i];
            for (size_t k = 0; k < colCount; ++k)
            {
                const ColumnInfo& column = columns[k];
                if ((column.index == index) && (column.visible == visible))
                    result.push_back (static_cast<int>(k));
            }
        }

        visible = !visible;
    }
    while (visible);

    return result;
}

void CSVNStatusListCtrl::ColumnManager::ApplyColumnOrder()
{
    // extract order of used columns from order of all columns

    int order[SVNSLC_MAXCOLUMNCOUNT+1];
    SecureZeroMemory (order, sizeof (order));

    std::vector<int> gridColumnOrder = GetGridColumnOrder();
    std::copy (gridColumnOrder.begin(), gridColumnOrder.end(), stdext::checked_array_iterator<int*>(&order[0], sizeof(order)));

    // we must have placed all columns or something is really fishy ..

    assert (gridColumnOrder.size() == columns.size());
    assert (GetColumnCount() == ((CHeaderCtrl*)(control->GetDlgItem(0)))->GetItemCount());

    // o.k., apply our column ordering

    control->SetColumnOrderArray (GetColumnCount(), order);
}

// utilities used when writing data to the registry

DWORD CSVNStatusListCtrl::ColumnManager::GetSelectedStandardColumns() const
{
    DWORD result = 0;
    for (size_t i = SVNSLC_NUMCOLUMNS; i > 0; --i)
        result = result * 2 + (columns[i-1].visible ? 1 : 0);

    return result;
}

CString CSVNStatusListCtrl::ColumnManager::GetUserPropList() const
{
    CString result;

    for (size_t i = 0, count = userProps.size(); i < count; ++i)
        result += userProps[i].name + ' ';

    return result;
}

CString CSVNStatusListCtrl::ColumnManager::GetShownUserProps() const
{
    CString result;

    for (size_t i = 0, count = columns.size(); i < count; ++i)
    {
        size_t index = static_cast<size_t>(columns[i].index);
        if (columns[i].visible && (index >= SVNSLC_USERPROPCOLOFFSET))
            result += userProps[index - SVNSLC_USERPROPCOLOFFSET].name
                    + ' ';
    }

    return result;
}

CString CSVNStatusListCtrl::ColumnManager::GetWidthString() const
{
    CString result;

    // regular columns

    TCHAR buf[10] = { 0 };
    for (size_t i = 0; i < SVNSLC_NUMCOLUMNS; ++i)
    {
        swprintf_s (buf, L"%08X", columns[i].width);
        result += buf;
    }

    // range with no column IDs

    result += CString ('0', 8 * (SVNSLC_USERPROPCOLOFFSET - SVNSLC_NUMCOLUMNS));

    // user-prop columns

    for (size_t i = 0, count = userProps.size(); i < count; ++i)
    {
        swprintf_s (buf, L"%08X", userProps[i].width);
        result += buf;
    }

    return result;
}

CString CSVNStatusListCtrl::ColumnManager::GetColumnOrderString() const
{
    CString result;

    TCHAR buf[3] = { 0 };
    for (size_t i = 0, count = columnOrder.size(); i < count; ++i)
    {
        if (columnOrder[i] < SVNSLC_MAXCOLUMNCOUNT)
        {
            swprintf_s (buf, L"%02X", columnOrder[i]);
            result += buf;
        }
    }

    return result;
}

// sorter utility class

CSVNStatusListCtrl::CSorter::CSorter ( ColumnManager* columnManager
                                      , CSVNStatusListCtrl * listControl
                                      , int sortedColumn
                                      , bool ascending)
                                      : columnManager (columnManager)
                                      , control(listControl)
                                      , sortedColumn (sortedColumn)
                                      , ascending (ascending)
{
    s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
    if (s_bSortLogical)
        s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
}

bool CSVNStatusListCtrl::CSorter::operator()
( const FileEntry* entry1
 , const FileEntry* entry2) const
{
#define SGN(x) ((x)==0?0:((x)>0?1:-1))

    int result = 0;
    switch (sortedColumn)
    {
    case 19:
        {
            if (result == 0)
            {
                __int64 fileSize1 = entry1->isfolder ? 0 : entry1->working_size != (-1) ? entry1->working_size : entry1->GetPath().GetFileSize();
                __int64 fileSize2 = entry2->isfolder ? 0 : entry2->working_size != (-1) ? entry2->working_size : entry2->GetPath().GetFileSize();

                result = int(fileSize1 - fileSize2);
            }
        }
    case 18:
        {
            if (result == 0)
            {
                __int64 writetime1 = entry1->GetPath().GetLastWriteTime();
                __int64 writetime2 = entry2->GetPath().GetLastWriteTime();

                FILETIME* filetime1 = (FILETIME*)(__int64*)&writetime1;
                FILETIME* filetime2 = (FILETIME*)(__int64*)&writetime2;

                result = CompareFileTime(filetime1,filetime2);
            }
        }
    case 17:
        {
            if (result == 0)
            {
                result = SGN(entry1->last_commit_date - entry2->last_commit_date);
            }
        }
    case 16:
        {
            if (result == 0)
            {
                result = entry1->remoterev - entry2->remoterev;
            }
        }
    case 15:
        {
            if (result == 0)
            {
                result = entry1->last_commit_rev - entry2->last_commit_rev;
            }
        }
    case 14:
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->last_commit_author, entry2->last_commit_author);
                else
                    result = StrCmpI(entry1->last_commit_author, entry2->last_commit_author);
            }
        }
    case 13:
        {
            if (result == 0)
            {
                result = (int)(entry1->lock_date - entry2->lock_date);
            }
        }
    case 12:
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->lock_comment, entry2->lock_comment);
                else
                    result = StrCmpI(entry1->lock_comment, entry2->lock_comment);
            }
        }
    case 11:
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->lock_owner, entry2->lock_owner);
                else
                    result = StrCmpI(entry1->lock_owner, entry2->lock_owner);
            }
        }
    case 10:
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->url, entry2->url);
                else
                    result = StrCmpI(entry1->url, entry2->url);
            }
        }
    case 9:
        {
            if (result == 0)
            {
                result = entry1->depth - entry2->depth;
            }
        }
    case 8:
        {
            if (result == 0)
            {
                result = entry1->remotepropstatus - entry2->remotepropstatus;
            }
        }
    case 7:
        {
            if (result == 0)
            {
                result = entry1->remotetextstatus - entry2->remotetextstatus;
            }
        }
    case 6:
        {
            if (result == 0)
            {
                result = entry1->propstatus - entry2->propstatus;
            }
        }
    case 5:
        {
            if (result == 0)
            {
                result = entry1->textstatus - entry2->textstatus;
            }
        }
    case 4:
        {
            if (result == 0)
            {
                result = entry1->remotestatus - entry2->remotestatus;
            }
        }
    case 3:
        {
            if (result == 0)
            {
                result = entry1->status - entry2->status;
            }
        }
    case 2:
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->path.GetFileExtension(), entry2->path.GetFileExtension());
                else
                    result = StrCmpI(entry1->path.GetFileExtension(), entry2->path.GetFileExtension());
            }
        }
    case 1:
        {
            // do not sort by file/dirname if the sorting isn't done by this specific column but let the second-order
            // sorting be done by path
            if ((result == 0)&&(sortedColumn == 1))
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->path.GetFileOrDirectoryName(), entry2->path.GetFileOrDirectoryName());
                else
                    result = StrCmpI(entry1->path.GetFileOrDirectoryName(), entry2->path.GetFileOrDirectoryName());
            }
        }
    case 0:     // path column
        {
            if (result == 0)
            {
                if (s_bSortLogical)
                    result = StrCmpLogicalW(entry1->path.GetWinPath(), entry2->path.GetWinPath());
                else
                    result = StrCmpI(entry1->path.GetWinPath(), entry2->path.GetWinPath());
            }
        }
    default:
        if ((result == 0) && (sortedColumn > 0))
        {
            // N/A props are "less than" empty props

            const CString& propName = columnManager->GetName (sortedColumn);

            auto propEntry1 = control->m_PropertyMap.find(entry1->GetPath());
            auto propEntry2 = control->m_PropertyMap.find(entry2->GetPath());
            bool entry1HasProp = (propEntry1 != control->m_PropertyMap.end()) && propEntry1->second.HasProperty (propName);
            bool entry2HasProp = (propEntry2 != control->m_PropertyMap.end()) && propEntry2->second.HasProperty (propName);

            if (entry1HasProp)
            {
                result = entry2HasProp
                    ? propEntry1->second[propName].Compare
                    (propEntry2->second[propName])
                    : 1;
            }
            else
            {
                result = entry2HasProp ? -1 : 0;
            }
        }
    } // switch (m_nSortedColumn)
    if (!ascending)
        result = -result;

    return result < 0;
}
